using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;


namespace Rogue
{
    // When we load PSD textures, they're loaded as a set of layers.
    // When they're loaded into the game, they deserialize into this struct, 
    // which gets created by deserialization (never constructed directly).
    class MultiTex
    {
        public List<string> m_Names = null;
        public List<Texture> m_Layers = null;
    }

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class RogueGame : Microsoft.Xna.Framework.Game
    {
        public static RogueGame Instance = null;

        private GameMap m_GameMap;
        public GameMap Map
            { get { return m_GameMap; } }

        private Mission m_Mission;
        public Mission CurMission
        { 
            get { return m_Mission; }
            set { m_Mission = value; }
        }

        private GraphicsDeviceManager m_Graphics;

        public RogueGame()
        {
            Instance = this;

            m_Graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }


        #region Serialization 
        public static void SaveFile<T>(T obj, string a_sFilename)
        {
            // Create the streams
            FileStream outputFileStream = File.Open(Path.Combine(StorageContainer.TitleLocation, a_sFilename), FileMode.Create, FileAccess.Write);
            GZipStream zipStream;

            // Object getting saved goes directly to memory stream
            MemoryStream memStream = new MemoryStream();
            byte[] buffer;

            BinaryFormatter Serializer = new BinaryFormatter();
            try
            {
                Serializer.Serialize(memStream, obj);

            }
            catch (System.Exception e)
            {
                ConsoleWindow.Instance.PrintError(e.Message);
            }

            // Save the memory stream to an array of bytes and close it
            buffer = new byte[memStream.Length];
            memStream.Seek(0, SeekOrigin.Begin);
            memStream.Read(buffer, 0, buffer.Length);
            memStream.Close();

            // ZipStream reads the buffer and spits out compressed data in the FileStream
            zipStream = new GZipStream(outputFileStream, CompressionMode.Compress, true);
            zipStream.Write(buffer, 0, buffer.Length);
            ConsoleWindow.Instance.PrintError("Uncompressed Size: " + buffer.Length.ToString() + " bytes");
            ConsoleWindow.Instance.PrintError("  Compressed Size: " + (zipStream.BaseStream.Length).ToString() + " bytes");
            zipStream.Close();
           
            // Close the file
            outputFileStream.Close();

  
        }

        public static T LoadFile<T>(string a_sFilename)
        {
            byte[] sizeBuffer;
            sizeBuffer = new byte[4];
            
            FileStream inputFileStream = File.Open(Path.Combine(StorageContainer.TitleLocation, a_sFilename), FileMode.OpenOrCreate, FileAccess.Read);
            MemoryStream memStream = new MemoryStream();
            byte[] bufferFile = new byte[inputFileStream.Length];
            inputFileStream.Read(bufferFile, 0, (int)(inputFileStream.Length - 4));
            
            inputFileStream.Position = (int)inputFileStream.Length - 4;
            inputFileStream.Read(sizeBuffer, 0, 4);
            inputFileStream.Position = 0;
            int nBufferLength = BitConverter.ToInt32(sizeBuffer, 0);

            GZipStream zipStream = new GZipStream(inputFileStream, CompressionMode.Decompress, true);
            //zipStream.Write(bufferFile, 0, nBufferLength);

            byte[] buffer = new byte[nBufferLength + 100];
            int nReadOffset = 0;
            int nTotalBytes = 0;

            while (true)
            {
                int nBytesRead = zipStream.Read(buffer, nReadOffset, 100);
                if (nBytesRead == 0)
                    break;

                nReadOffset += nBytesRead;
                nTotalBytes += nBytesRead;
            }

            zipStream.Close();            
            memStream.Write(buffer, 0, nTotalBytes);
            memStream.Seek(0, SeekOrigin.Begin);

            BinaryFormatter Serializer = new BinaryFormatter();
            try
            {
                T obj = (T)Serializer.Deserialize(memStream);
                memStream.Close();
                inputFileStream.Close();
                return obj;
            }
            catch
            {
                memStream.Close();
                inputFileStream.Close();
                throw new System.Exception("Error loading file!");
            }


        }
        #endregion

        #region Private Static Member Variables

#if DEBUG
        private static bool bGenerateButtonWasPressed;
#endif

        private static TextWindow m_StatusBar;
        #endregion

        public static class NativeMethods
        {
            public const byte VK_NUMLOCK = 0x90;
            public const uint KEYEVENTF_EXTENDEDKEY = 1;
            public const int KEYEVENTF_KEYUP = 0x2;

            [DllImport("user32.dll")]
            public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);

            [DllImport("user32.dll")]
            public static extern short GetKeyState(int keyCode);

            public static void SimulateNumlockPress()
            {
                keybd_event(VK_NUMLOCK, 0x45, KEYEVENTF_EXTENDEDKEY, 0);
                keybd_event(VK_NUMLOCK, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
            }
        }

        /// <summary>
        /// Sets graphics device and game class up
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            m_Graphics.PreferredBackBufferWidth  = 1280;   // Window Width
            m_Graphics.PreferredBackBufferHeight = 720;   // Window Height        
            //m_Graphics.SynchronizeWithVerticalRetrace = false;
            m_Graphics.ApplyChanges();

            this.IsFixedTimeStep = false;
            base.IsMouseVisible = true;

            // make sure numlock is on
            bool NumLock = (NativeMethods.GetKeyState((int)Keys.NumLock)) != 0;
            if (!NumLock)
            {
                NativeMethods.SimulateNumlockPress();
            }

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            Texture2D tLargeFont = Content.Load<Texture2D>("curses");
            Texture2D tSmallFont = Content.Load<Texture2D>("font_proggy");
            
            DetailWindow.HeroWindow = new DetailWindow(
                tSmallFont,
                tLargeFont,
                m_Graphics.GraphicsDevice,
                new Point(16, 32),
                new Point(35, 38),
                new Point(16, 16),
                new Point(-8, 0),
                64
                );
            DetailWindow.HeroWindow.BorderColor = Color.BurlyWood;

            int enemyWidth = DetailWindow.HeroWindow.WindowWidth + 64;
            DetailWindow.EnemyWindow = new DetailWindow(
                tSmallFont,
                tLargeFont,
                m_Graphics.GraphicsDevice,
                new Point(Window.ClientBounds.Width - enemyWidth - 16, 32),
                new Point(enemyWidth/8, 38),
                new Point(16, 16),
                new Point(-8, 0),
                64
                );
            DetailWindow.EnemyWindow.BorderColor = Color.PaleGoldenrod;




            TutWindow.Instance = new TutWindow(
                tSmallFont,
                m_Graphics.GraphicsDevice,
                new Point(DetailWindow.HeroWindow.WindowBounds.Right + 60, DetailWindow.HeroWindow.Position.Y), // pos
                new Point(55, 5),   // cellmap size
                new Point(16, 16),   // cell screen size
                new Point(-8, 0));


            ConsoleWindow.Instance = new ConsoleWindow(
                tSmallFont,
                m_Graphics.GraphicsDevice,
                new Point(0, 624),
                new Point(160, 6),
                new Point(16, 16),
                new Point(-8, 0),
                64
                );

            ConsoleWindow.Instance.RegisterFunction("MAP.LOAD", CONSOLE_LoadMap);
            ConsoleWindow.Instance.RegisterFunction("MAP.SAVE", CONSOLE_SaveMap);

            TerrainWindow.Instance = new TerrainWindow(
                tLargeFont,
                m_Graphics.GraphicsDevice,
                new Point(0, 16),
                new Point(80, 38),
                new Point(16, 16)
                );
        

            m_StatusBar = new TextWindow(
                tSmallFont,
                m_Graphics.GraphicsDevice,
                new Point(0, 0),
                new Point(160, 1),
                new Point(16, 16),
                new Point(-8, 0)
                );
            m_StatusBar.BorderBottom = true;
            m_StatusBar.BorderColor = new Color(60, 60, 60);
            m_StatusBar.DrawBackground = true;
            m_StatusBar.BackgroundColor = new Color(10, 10, 10) ;

            TerrainDatabase.Instance.LoadDefinitions();
            EntityDatabase.Instance.LoadDefinitions();

            

            m_GameMap = new GameMap();  // cell grid replaced by map generator...

            LabelHelper.Instance = new LabelHelper(m_Graphics.GraphicsDevice, tSmallFont, m_GameMap);
            MapGenerator mapGenerator = new MapGenerator(m_GameMap);

            // this is the beginning of our hardcoded little mission - from the forest to the goblin caves
            mapGenerator.Run(new MapGenerator.ForestParams());

            // the map generator moves the hero, move the camera to him
            m_GameMap.Camera.SetPosition(m_GameMap.GetEntity("HERO").Position);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

#if DEBUG
        protected void CheckGenerate()
        {
            GamePadState PlayerOneState = GamePad.GetState(PlayerIndex.One);
            KeyboardState KBState = Keyboard.GetState();

            bool bGenerateButtonIsPressed = (PlayerOneState.Buttons.Start == ButtonState.Pressed ||
                                            KBState.IsKeyDown(Keys.NumPad0));

            if (bGenerateButtonIsPressed && !bGenerateButtonWasPressed)
            {
                //AddMessage("Generating map...", Color.WhiteSmoke);
                bGenerateButtonWasPressed = true;

                RunMapGenerator();
            }

            if (!ConsoleWindow.Instance.IsActive)
                m_GameMap.m_AllVisible = KBState.IsKeyDown(Keys.V);

            if (!bGenerateButtonIsPressed && bGenerateButtonWasPressed)
                bGenerateButtonWasPressed = false;
        }
#else
        protected void CheckGenerate() { }
#endif

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime a_GameTime)
        {
            Timeline.Instance.LastGameTime = a_GameTime;

            if (!RogueGame.Instance.IsActive)
                return;

            KeyboardState KBState = Keyboard.GetState();
            GamePadState PlayerOneState = GamePad.GetState(PlayerIndex.One);
            InputManager.Instance.Update(a_GameTime);
            // Allows the game to exit
            if (PlayerOneState.Buttons.Back == ButtonState.Pressed || KBState.IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }

            CheckGenerate();
            
            DetailWindow.UpdateInput();
            DetailWindow.HeroWindow.Update(a_GameTime);
            DetailWindow.EnemyWindow.Update(a_GameTime);

            TutWindow.Instance.Update(a_GameTime);

            ConsoleWindow.Instance.Update(a_GameTime);
            LabelHelper.Instance.Update(a_GameTime);

            m_GameMap.Update(a_GameTime);
            
            if (m_Mission != null)
                m_Mission.Update(a_GameTime);
            
            base.Update(a_GameTime);
        }

        // maybe this belongs in the map generator... 
        static MapGenerator.DungeonParams DungeonParamsForDepth(int d)
        {
            MapGenerator.DungeonParams dp = new MapGenerator.DungeonParams();

            dp.width = Math.Min(20 + 5 * d, 55);
            dp.height = Math.Min(20 + 5 * d, 55);

            dp.roomDensity = 0.04f + 0.01f * Math.Min(5, d);      // deep rooms are more common
            dp.roomMinWidth =   5 - Math.Min(3, d);
            dp.roomMinHeight =  5 - Math.Min(3, d);
            dp.roomMaxHeight =  10 - Math.Min(5, d);
            dp.roomMaxWidth =   10 - Math.Min(5, d);                // and smaller

            dp.extraCorridorChance = 0.05f - 0.01f * Math.Min(4, d);   // and less connected

            dp.deadEndChance = 0.01f * Math.Min(10, d);             // and have more dead ends
            dp.deadEndMinLength = 3 * d;
            dp.deadEndMaxLength = 15 * d;

            dp.doorChance = 0.2f;

            dp.roomToRoomCorr.maxAng = 0.1f * Math.Min(5, d);                            // and twistier passages
            dp.roomToRoomCorr.angStep = 0.05f;
            dp.roomToRoomCorr.noise = 0.04f * Math.Min(10, d);

            dp.deadEndCorr.maxAng = 1.5f - 0.05f * Math.Min(10, d);     // where dead ends look like room-room connections
            dp.deadEndCorr.angStep = 0.05f;
            dp.deadEndCorr.noise = 1.0f - 0.06f * Math.Min(10, d);

            dp.goblinDensity = 0.004f;     // ignores level - deeper levels get more anyway

            return dp;
        }

        // Handles cleanup, camera connection, etc.
        public void RunMapGenerator()
        {
            m_GameMap.CurrentDepth++;

            m_GameMap.RemoveAllEntitiesButPlayer();

            if (m_GameMap.CurrentDepth == 4)    // won the game
            {
                MapGenerator.ForestParams f = new MapGenerator.ForestParams();
                f.hasWon = true;        // no missions, tiny world
                f.height = 23;
                f.width = 23;
                f.grassChance = 0.4f;
                f.darkGrassChance = 0.4f;

                MapGenerator mapGenerator = new MapGenerator(m_GameMap);
                mapGenerator.Run(f);
            }
            else
            {
                MapGenerator mapGenerator = new MapGenerator(m_GameMap);
                mapGenerator.Run(DungeonParamsForDepth(m_GameMap.CurrentDepth));
            }

            // the map generator moves the hero, move the camera to him
            Entity hero = m_GameMap.GetHero();
            m_GameMap.Camera.SetPosition(hero.Position);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            m_Graphics.GraphicsDevice.Clear(Color.Black);
            m_GameMap.Draw(TerrainWindow.Instance);
            
            TerrainWindow.Instance.Draw(GraphicsDevice);
            LabelHelper.Instance.Draw(GraphicsDevice);
            m_StatusBar.Draw(GraphicsDevice);
            
            bool consoleOnTop = ConsoleWindow.Instance.Height > 6;
            if (!consoleOnTop)
                ConsoleWindow.Instance.Draw(GraphicsDevice);

            DetailWindow.HeroWindow.Draw(GraphicsDevice);
            DetailWindow.EnemyWindow.Draw(GraphicsDevice);
            TutWindow.Instance.Draw(GraphicsDevice);

            if (consoleOnTop)
                ConsoleWindow.Instance.Draw(GraphicsDevice);            
            
            base.Draw(gameTime);
        }


        #region Console Commands
        private void CONSOLE_SaveMap(string[] a_sArgs)
        {
            if (a_sArgs[0].Length > 0)
                SaveFile<GameMap>(m_GameMap, a_sArgs[0] + ".map");
        }

        private void CONSOLE_LoadMap(string[] a_sArgs)
        {
            if (a_sArgs[0].Length > 0)
                m_GameMap = LoadFile<GameMap>(a_sArgs[0] + ".map");
        }



        public static void AddStatusString(string a_sText, int a_X, Color a_Color, CellWindow.Alignment a_Align)
        {
            m_StatusBar.PutString(a_sText, a_X, 0, a_Color, a_Align);
        }

        #endregion

    }
}





